package com.nami.ucenter.infra.acl.repository.user;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.nami.gate.external.basic.cache.user.DeviceInfo;
import com.nami.gate.external.basic.cache.user.UserDeviceRel;
import com.nami.gate.external.basic.event.EventPublisher;
import com.nami.gate.external.basic.event.enums.EventType;
import com.nami.gate.external.basic.event.model.CommonEvent;

import com.nami.ucenter.domain.model.UserLoginModel;
import com.nami.ucenter.domain.port.repository.CacheUserRepository;
import com.nami.ucenter.infra.core.cache.OnlUserCache;
import com.nami.ucenter.types.utils.RedisKeyUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.redis.utils.RedisTemplateKit;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.nami.gate.external.basic.constants.CommonConst.COLON;
import static org.dromara.common.core.constant.RedisConst.CACHE_PREFIX;

@Slf4j
@Service
@RequiredArgsConstructor
public class CacheUserRepositoryImpl implements CacheUserRepository {

    private final RedisTemplateKit redisTemplateKit;
    private final RedissonClient redissonClient;
    private final EventPublisher eventPublisher;

    @Override
    public List<DeviceInfo> getOnlDevicesByUserId(String appKey, String userId, Integer excludeDeviceType) {
        UserDeviceRel l1user = OnlUserCache.get(userId);
        if (l1user != null) {
            return l1user.getDeviceInfos();
        }
        UserDeviceRel userWrap = (UserDeviceRel) redisTemplateKit.hget(RedisKeyUtil.buildOnlineUserCacheKey(appKey, userId), userId);
        if (userWrap != null) {
            OnlUserCache.add(userId, userWrap);
            List<DeviceInfo> onlineClients = new ArrayList<>();
            List<DeviceInfo> deviceInfos = userWrap.getDeviceInfos();
            for (DeviceInfo deviceInfo : deviceInfos) {
                if (deviceInfo == null) {
                    continue;
                }
                if (excludeDeviceType == null || !deviceInfo.getPlatform().equals(excludeDeviceType)) {
                    onlineClients.add(deviceInfo);
                }
            }
            return onlineClients;

        }
        return null;
    }

    @Override
    public boolean updOnlUser(UserDeviceRel userDevice) {
        OnlUserCache.add(userDevice.getUserId(), userDevice);
//        redisUtil.hPutIfAbsent(CACHE_PREFIX + userReq.getAppKey(), userReq.getUserId(), userReq.getUserInfo());
        redisTemplateKit.hset(RedisKeyUtil.buildOnlineUserCacheKey(userDevice.getAppKey(), userDevice.getUserId()),
            userDevice.getUserId(), userDevice);

        // 在线
        redisTemplateKit.hset(buildOnlineFlag(userDevice.getAppKey()), userDevice.getUserId(), "1");
        // 删除其他redis信息使用
//        redisTemplateUtil.sSet(CACHE_PREFIX + dto.getNettyServerInfo(), identityNo);

        return Boolean.TRUE;
    }


    @Override
    public UserDeviceRel getOnlUser(String appKey, String userId) {
        UserDeviceRel userWithDeviceInfo = OnlUserCache.get(userId);
        if (userWithDeviceInfo != null) {
            return userWithDeviceInfo;
        }
        UserDeviceRel userDevice = (UserDeviceRel) redisTemplateKit.hget(RedisKeyUtil.buildOnlineUserCacheKey(appKey, userId), userId);
        if (userDevice == null) {
            return null;
        }
        OnlUserCache.add(userId, userDevice);
        return userDevice.clone();
    }

    public List<UserDeviceRel> getOnlUsers(String appKey, List<String> userIds) {
        TimeInterval timer = DateUtil.timer();
        if (StringUtils.isBlank(appKey) || CollUtil.isEmpty(userIds)) {
            return null;
        }
        List<UserDeviceRel> result = new ArrayList<>(userIds.size());
        List<String> qryCaches = new ArrayList<>(userIds.size());
        for (String userId : userIds) {
            UserDeviceRel userWithDeviceInfo = OnlUserCache.get(userId);
            if (userWithDeviceInfo != null) {
                result.add(userWithDeviceInfo);
                continue;
            }
            qryCaches.add(userId);
        }
        if (CollUtil.isNotEmpty(qryCaches)) {
            List<UserDeviceRel> users = redisTemplateKit.hMultiGetAll(RedisKeyUtil.buildOnlineUserCacheKey(appKey, null),
                qryCaches);
            if (CollUtil.isEmpty(users)) {
                return result;
            }
            for (UserDeviceRel user : users) {
                if (user == null) {
                    continue;
                }
                OnlUserCache.add(user.getUserId(), user);
                result.add(user);
            }

        }

        if (timer.interval() > 100) {
            log.warn("[user-cache] 查询用户信息出现耗时高。size={}, use={}ms", result.size(), timer.interval());
        }
        return result;
    }

    @Override
    public DeviceInfo getDeviceInfo(String appKey, String userId, Integer deviceType) {
        List<DeviceInfo> onlineClients = this.getOnlDevicesByUserId(appKey, userId, null);
        if (CollUtil.isEmpty(onlineClients)) {
            return null;
        }
        for (DeviceInfo onlineClient : onlineClients) {
            if (onlineClient.getPlatform().equals(deviceType)) {
                return onlineClient;
            }
        }

        return null;
    }

    @Override
    public boolean userLoginWithLock(UserLoginModel userModel) {
        RLock lock = redissonClient.getLock("nami:" + userModel.getAppKey() + ":user:login:lock:" + userModel.getUserId());
        try {
            lock.lock(2, TimeUnit.SECONDS);
            UserDeviceRel userDevice = this.getOnlUser(userModel.getAppKey(), userModel.getUserId());
            if (userDevice != null && isUserAlreadyLoggedIn(userModel, userDevice)) {
                return Boolean.FALSE;
            }

            // 更新用户登录信息
            updateUserLoginInfo(userModel, userDevice);
            return Boolean.TRUE;
        } finally {
            lock.unlock();
        }

    }

    public String getUserMqTags(String appKey, String userId, String instanceId) {
        UserDeviceRel userFromCache = this.getOnlUser(appKey, userId);
        if (userFromCache == null) {
            log.warn("[logout] 设备超时, 移除失败，找不到user userId={}.", userId);
            return null;
        }
        List<DeviceInfo> deviceInfos = userFromCache.getDeviceInfos();
        if (CollUtil.isEmpty(deviceInfos)) {
            log.warn("[logout] 当前设备为空.");
            return null;
        }
        DeviceInfo deviceInfo = deviceInfos.stream()
            .filter(item -> item != null && instanceId.equals(item.getInstanceId()))
            .findFirst().orElse(null);
        if (deviceInfo == null) {
            log.warn("[logout] 设备超时, 移除失败.找不到缓存中设备信息。userId={}", userId);
            return null;
        }
        return deviceInfo.getConnectId();
    }

    @Override
    public boolean removeOnlDevice(String appKey, String userId, String instanceId) {
        log.info("[nami-redis] 开始删除用户绑定的缓存信息 appKey:{}, userId={}, instanceId={}", appKey, userId, instanceId);

        RLock lock = redissonClient.getLock(CACHE_PREFIX + appKey + ":user:lock:" + userId);
        try {
            lock.lock(2, TimeUnit.SECONDS);
            UserDeviceRel user = (UserDeviceRel) redisTemplateKit.hget(RedisKeyUtil.buildOnlineUserCacheKey(appKey, userId), userId);
            if (user != null) {
                List<DeviceInfo> deviceInfos = user.getDeviceInfos();
                if (CollUtil.isNotEmpty(deviceInfos) && deviceInfos.size() <= 1) {
                    user.setDeviceInfos(null);
                    OnlUserCache.add(user.getUserId(), user);
                    redisTemplateKit.hdel(RedisKeyUtil.buildOnlineUserCacheKey(appKey, userId), userId);
                    redisTemplateKit.hdel(buildOnlineFlag(appKey), userId);
                } else {
                    List<DeviceInfo> newDevices = deviceInfos.stream()
                        .filter(item -> item.getInstanceId().equals(instanceId)).distinct().collect(Collectors.toList());
                    user.setDeviceInfos(newDevices);
                    redisTemplateKit.hset(RedisKeyUtil.buildOnlineUserCacheKey(appKey, userId),
                        userId, user);
                    OnlUserCache.add(user.getUserId(), user);
                }
            }

        } finally {
            lock.unlock();
        }

        // TODO 发mq广播消息，通知所有logic服务 删除一级缓存得user当前在线设备数据
        // TODO 如果是改为了user服务，那么则是发送mq 广播到所有user服务
        eventPublisher.publish(new CommonEvent(EventType.REFRESH_L1_USER_CACHE, userId));

// 待删除
//        redisUtil.hdel(CACHE_PREFIX + request.getAppKey() + COLON + "user" + COLON + request.getUserId(), DeviceEnum.getDeviceNameByValue(request.getDeviceType()));
//        redisUtil.hdel(CACHE_PREFIX + request.getAppKey(), IdentityUtil.buildIdentityNo(request.getUserId(), request.getDeviceType()));
//        redisUtil.hdel(CACHE_PREFIX + request.getAppKey() + ":user:device", request.getUserId());
        // 此处需要与其他参数共同校验才能成立 保证所有类型客户端都不在线才能是不在线状态
//        redisUtil.hset(CACHE_PREFIX + request.getAppKey() + COLON + "user:online", request.getUserId(), "0");

        return Boolean.TRUE;
    }

    private void updateUserLoginInfo(UserLoginModel userModel, UserDeviceRel deviceInfo) {
        List<DeviceInfo> devices = deviceInfo != null && deviceInfo.getDeviceInfos() != null ?
            deviceInfo.getDeviceInfos().stream()
                .filter(item -> !item.getInstanceId().equals(userModel.getInstanceId()))
                .collect(Collectors.toList()) : null;
        this.updOnlUser(makeUserWithDevice(userModel, devices));

        // TODO 发mq广播消息，通知所有logic服务 删除一级缓存得user当前在线设备数据
        // TODO 如果是改为了user服务，那么则是发送mq 广播到所有user服务
        eventPublisher.publish(new CommonEvent(EventType.REFRESH_L1_USER_CACHE, userModel.getUserId()));
    }

    private static UserDeviceRel makeUserWithDevice(UserLoginModel userModel, List<DeviceInfo> devices) {
        UserDeviceRel userInfo = new UserDeviceRel();
        userInfo.setNick(userModel.getNick());

        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.setInstanceId(userModel.getInstanceId());
        deviceInfo.setConnectId(userModel.getConnectId());

        deviceInfo.setUserIp(userModel.getClientIp());
        deviceInfo.setPlatform(userModel.getPlatform());

        if (CollUtil.isEmpty(devices)) {
            userInfo.setDeviceInfos(List.of(deviceInfo));
        } else {
            devices.add(deviceInfo);
            userInfo.setDeviceInfos(devices);
        }

        userInfo.setAppKey(userModel.getAppKey());
        userInfo.setFirstLoginIp(userModel.getClientIp());
        userInfo.setUserId(userModel.getUserId());
        userInfo.setAvatar(userModel.getAvatar());
        userInfo.setStatus(userModel.getStatus());

        return userInfo;
    }

    private boolean isUserAlreadyLoggedIn(UserLoginModel userModel, UserDeviceRel user) {
        if (user.getDeviceInfos() == null) {
            return Boolean.FALSE;
        }
        return user.getDeviceInfos().stream()
            .anyMatch(device -> device.getPlatform().equals(userModel.getPlatform()) &&
                StringUtils.isNotBlank(userModel.getClientIp()) &&
                userModel.getClientIp().equals(device.getUserIp()));
    }

    private static @NotNull String buildOnlineFlag(String appKey) {
        return CACHE_PREFIX + appKey + COLON + "user:online";
    }

    private static @NotNull String buildOnlineDeviceKey(String appKey, String userId) {
        return CACHE_PREFIX + appKey + COLON + "user" + COLON + userId;
    }


//    public static String generateP2PId(SequenceDTO sequenceDTO) {
//        int i = sequenceDTO.getUserNo().compareTo(sequenceDTO.getUserNo2());
//        if (i < 0) {
//            return sequenceDTO.getUserNo2() + "|" + sequenceDTO.getUserNo();
//        } else if (i > 0) {
//            return sequenceDTO.getUserNo() + "|" + sequenceDTO.getUserNo2();
//        }
//        throw new RuntimeException("会话sequence错误");
//    }

}
